package com.task.controller;

import com.alibaba.fastjson.JSONArray;
import com.alipay.api.internal.util.file.IOUtils;
import com.basemodel.Result;
import com.task.service.TaskDemo;
import com.util.SpringJobBeanFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author: cxt
 * @time: 2021/7/14
 * <p>
 * 前端框架请求
 */
@Controller
@Slf4j
public class IndexController {
    private RedisTemplate<String, Object> redisTemplate;
    private TaskDemo taskDemo;

    @GetMapping(value = {"/", "/index"})
    public String index() {
        return "/index";
    }

    @GetMapping(value = {"/home"})
    public String home() {
        return "/page/home";
    }

    @GetMapping(value = {"/love"})
    public String love() {
        return "/page/love";
    }

    // 返回本地mp3文件
    @GetMapping("/getMp3")
    public void mp3File(HttpServletResponse response) throws IOException {
        InputStream inputStream = null;
        ServletOutputStream outputStream = null;
        try {
            ClassPathResource resource = new ClassPathResource("/json/love.mp3");
            inputStream = resource.getInputStream();
            response.setContentType("audio/mp3");
            response.addHeader("Content-Length", "" + inputStream.available());
            log.info("读取文件数据：{}", inputStream.available());
            outputStream = response.getOutputStream();
            IOUtils.copy(inputStream, outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (!Objects.isNull(inputStream)) {
                inputStream.close();
            }
            if (!Objects.isNull(outputStream)) {
                outputStream.close();
            }
        }
    }

    @GetMapping(value = {"/player"})
    public String player() {
        return "/page/dplayer";
    }

    /**
     * 分段下载视频
     */
    @GetMapping("/getVideo")
    @ResponseBody
    public void getVideo(@RequestParam("filePath") String filePath, HttpServletRequest request, HttpServletResponse response) {
        String range = request.getHeader("Range");
        log.info("current request rang:" + range);
        File file = new File("src/main/resources/json/" + filePath);

        //开始下载位置
        long startByte = 0;
        //结束下载位置
        long endByte = file.length() - 1;
        log.info("文件开始位置：{}，文件结束位置：{}，文件总长度：{}", startByte, endByte, file.length());

        //有range的话
        if (range != null && range.contains("bytes=") && range.contains("-")) {
            range = range.substring(range.lastIndexOf("=") + 1).trim();
            String[] ranges = range.split("-");
            try {
                //判断range的类型
                if (ranges.length == 1) {
                    //类型一：bytes=-2343
                    if (range.startsWith("-")) {
                        endByte = Long.parseLong(ranges[0]);
                    }
                    //类型二：bytes=2343-
                    else if (range.endsWith("-")) {
                        startByte = Long.parseLong(ranges[0]);
                    }
                }
                //类型三：bytes=22-2343
                else if (ranges.length == 2) {
                    startByte = Long.parseLong(ranges[0]);
                    endByte = Long.parseLong(ranges[1]);
                }

            } catch (NumberFormatException e) {
                startByte = 0;
                endByte = file.length() - 1;
                log.error("Range Occur Error,Message:{}", e.getLocalizedMessage());
            }
        }

        //要下载的长度
        long contentLength = endByte - startByte + 1;
        //文件名
        String fileName = file.getName();
        //文件类型
        String contentType = request.getServletContext().getMimeType(fileName);

        // 解决下载文件时文件名乱码问题
        byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
        fileName = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);

        //各种响应头设置
        //支持断点续传，获取部分字节内容：
        response.setHeader("Accept-Ranges", "bytes");
        //http状态码要为206：表示获取部分内容
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        response.setContentType(contentType);
        response.setHeader("Content-Type", contentType);
        //inline表示浏览器直接使用，attachment表示下载，fileName表示下载的文件名
        response.setHeader("Content-Disposition", "inline;filename=" + fileName);
        response.setHeader("Content-Length", String.valueOf(contentLength));
        // Content-Range，格式为：[要下载的开始位置]-[结束位置]/[文件总大小]
        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());

        BufferedOutputStream outputStream = null;
        RandomAccessFile randomAccessFile = null;
        //已传送数据大小
        long transmitted = 0;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
            outputStream = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[4096];
            int len = 0;
            randomAccessFile.seek(startByte);
            //warning：判断是否到了最后不足4096（buff的length）个byte这个逻辑（(transmitted + len) <= contentLength）要放前面
            //不然会会先读取randomAccessFile，造成后面读取位置出错;
            while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
                outputStream.write(buff, 0, len);
                transmitted += len;
            }
            //处理不足buff.length部分
            if (transmitted < contentLength) {
                len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
                outputStream.write(buff, 0, len);
                transmitted += len;
            }

            outputStream.flush();
            response.flushBuffer();
            randomAccessFile.close();
            log.info("下载完毕：" + startByte + "-" + endByte + "：" + transmitted);
        } catch (ClientAbortException e) {
            log.warn("用户停止下载：" + startByte + "-" + endByte + "：" + transmitted);
            //捕获此异常表示拥护停止下载
        } catch (IOException e) {
            e.printStackTrace();
            log.error("用户下载IO异常，Message：{}", e.getLocalizedMessage());
        } finally {
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @GetMapping("/task/pcTable")
    public String pcTable() {
        return "/pcTable";
    }

    /**
     * layui 模板数据
     * 表格查询数据
     *
     * @param name 数据文件名称
     * @return 数据
     */
    @GetMapping("/tableData")
    @ResponseBody
    public HashMap<String, Object> getTableData(String name) {
        if (name == null) {
            return null;
        }
        HashMap<String, Object> map = new HashMap<>();
        JSONArray array = redJson(name);
        map.put("data", array);
        map.put("code", 200);
        map.put("count", array.size());
        HashMap<String, String> row = new HashMap<>();
        row.put("experience", "650");
        row.put("logins", "999");
        map.put("totalRow", row);
        map.put("msg", "");
        return map;
    }

    /**
     * layui模板数据
     *
     * @return 表格数据
     */
    @GetMapping("/get")
    @ResponseBody
    public ArrayList<Map<String, String>> getList() {
        ArrayList<Map<String, String>> list = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("title", "aaa1");
        map.put("key", "aaa2");
        map.put("value", "aaa3");
        list.add(map);
        return list;
    }

    @GetMapping("/implementOne")
    @ResponseBody
    public Result<String> multitaskingOne() {
        log.info("开始");
        long begin = System.currentTimeMillis();
        try {
            Future<Integer> test1 = taskDemo.test1();
            Future<Integer> test2 = taskDemo.test2();
            Future<Integer> test3 = taskDemo.test3();
            Future<Integer> test4 = taskDemo.test4();
            Future<Integer> test5 = taskDemo.test5();

            Integer integer1 = 0;
            Integer integer2 = 0;
            Integer integer3 = 0;
            Integer integer4 = 0;
            Integer integer5 = 0;

            // 等待其他线程执行完毕,任务没有执行结束就一直等待，可以设置get的超时时间
            // test1.get(60, TimeUnit.SECONDS); // 设置超时时间，时间单位是秒
            // .isDone() 是判断线程执行完毕的标志，执行完毕返回true
            while (true) {
                boolean done1 = test1.isDone();
                boolean done2 = test2.isDone();
                boolean done3 = test3.isDone();
                boolean done4 = test4.isDone();
                boolean done5 = test5.isDone();

                boolean b = done1 & done2 & done3 & done4 & done5;
                if (b) {
                    // .get()方法获取线程执行结果，具有阻塞状态，所以需要死循环进行判断
                    integer1 = test1.get();
                    integer2 = test2.get();
                    integer3 = test3.get();
                    integer4 = test4.get();
                    integer5 = test5.get();
                    break;
                }
            }

            int i = integer1 + integer2 + integer3 + integer4 + integer5;
            log.info(i + "$");// 执行结果
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        long l = System.currentTimeMillis() - begin;
        log.info("执行任务完成：耗时{}", l);
        return Result.success("操作成功", "耗时：" + l);
    }

    /**
     * 上述方法作比较
     */
    @GetMapping("/implementTwo")
    @ResponseBody
    public Result<String> multitaskingTwo() {
        ThreadPoolTaskExecutor executor = SpringJobBeanFactory.getBean("myAsyncExecutor");
        long begin = System.currentTimeMillis();

        // 对应上述的testX方法
        CompletableFuture<Integer> test1 = CompletableFuture.supplyAsync(() -> {
            String name = Thread.currentThread().getName();
            log.info("线程名称：" + name);
            try {
                // 模拟一系列数据操作，耗时1500
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1500;
        }, executor);

        CompletableFuture<Integer> test2 = CompletableFuture.supplyAsync(() -> {
            String name = Thread.currentThread().getName();
            log.info("线程名称：" + name);
            try {
                // 模拟一系列数据操作，耗时200
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 200;
        }, executor);

        CompletableFuture<Integer> test3 = CompletableFuture.supplyAsync(() -> {
            String name = Thread.currentThread().getName();
            log.info("线程名称：" + name);
            try {
                // 模拟一系列数据操作，耗时400
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 400;
        }, executor);
        CompletableFuture<Integer> test4 = CompletableFuture.supplyAsync(() -> {
            String name = Thread.currentThread().getName();
            log.info("线程名称：" + name);
            try {
                // 模拟一系列数据操作，耗时900
                Thread.sleep(900);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 900;
        }, executor);

        CompletableFuture<Integer> test5 = CompletableFuture.supplyAsync(() -> {
            String name = Thread.currentThread().getName();
            log.info("线程名称：" + name);
            try {
                // 模拟一系列数据操作，耗时1000
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1000;
        }, executor);

        CompletableFuture<Void> all = CompletableFuture.allOf(test1, test2, test3, test4, test5);
        // all.get(); // 抛出的是经过检查的异常，ExecutionException, InterruptedException 需要用户手动处理
        all.join(); // 抛出的是uncheck异常（即RuntimeException),不会强制开发者抛出
        // 结果
        int result = 0;
        try {
            Integer integer1 = test1.get();
            Integer integer2 = test2.get();
            Integer integer3 = test3.get();
            Integer integer4 = test4.get();
            Integer integer5 = test5.get();
            result = integer1 + integer2 + integer3 + integer4 + integer5;
            log.info("执行完成：结果{}", result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        long l = System.currentTimeMillis() - begin;
        log.info("执行任务完成：耗时{}", l);
        return Result.success("操作成功", "耗时：" + l + ";结果：" + result);
    }

    /**
     * 前端数据放入redis 测试
     *
     * @param user 用户
     * @param pwd  密码
     * @return
     */
    @GetMapping("/login")
    @ResponseBody
    public Object login(@RequestParam("user") String user, @RequestParam("pwd") String pwd) {
        verification(user, pwd); // 假设这是登录验证
        return redisTemplate.boundValueOps("userInfo").get();
    }

    public void verification(String user, String pwd) {
        HashMap<String, String> map = new HashMap<>();
        map.put("user", user);
        StringBuilder sb = new StringBuilder(pwd);
        int index = pwd.length() / 2;
        sb.insert(index, "****");
        map.put("pwd", sb.toString());
        redisTemplate.boundValueOps("userInfo").set(map);
    }

    @Autowired
    public void setTaskDemo(TaskDemo taskDemo) {
        this.taskDemo = taskDemo;
    }

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 读取文件
    public static JSONArray redJson(String name) {
        InputStream in = Resource.class.getResourceAsStream("/json/" + name);
        if (in != null) {
            byte[] bytes = new byte[0];
            try {
                bytes = new byte[in.available()];
                in.read(bytes);
                String s = new String(bytes);
                in.close();
                return JSONArray.parseArray(s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
